﻿using AutoMapper;
using JuCheap.Core.Data.Entity;
using JuCheap.Core.Infrastructure.Exceptions;
using JuCheap.Core.Infrastructure.Extentions;
using JuCheap.Core.IRepository;
using JuCheap.Core.IService.AppService;
using JuCheap.Core.Model;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace JuCheap.Core.Service.AppService
{
    /// <summary>
    /// 菜单扩展服务
    /// </summary>
    public class MenuService : IMenuService
    {
        private readonly ISysMenuRepository _sysMenuRepository;
        private readonly IUnitWork _unitWork;
        private readonly IMapper _mapper;

        /// <summary>
        /// ctor
        /// </summary>
        public MenuService(IUnitWork unitWork, ISysMenuRepository sysMenuRepository, IMapper mapper)
        {
            _unitWork = unitWork;
            _sysMenuRepository = sysMenuRepository;
            _mapper = mapper;
        }

        /// <summary>
        /// 创建菜单
        /// </summary>
        /// <param name="menuReq">菜单请求信息</param>
        /// <returns>用户Id</returns>
        public async Task<string> CreateAsync(MenuCreateRequestDto menuReq)
        {
            if (await _sysMenuRepository.ExistsAsync(x => x.ParentId == menuReq.ParentId && x.Name == menuReq.Name))
            {
                throw new BusinessException("菜单名称重复");
            }
            if (await _sysMenuRepository.ExistsAsync(x => x.Code == menuReq.Code))
            {
                throw new BusinessException("菜单编号重复");
            }
            var menu = _mapper.Map<SysMenu>(menuReq);
            var menuId = await _sysMenuRepository.InsertAsync(menu);
            return menuId;
        }

        /// <summary>
        /// 修改用户
        /// </summary>
        /// <param name="menuReq">用户请求信息</param>
        /// <returns>用户Id</returns>
        public async Task<string> UpdateAsync(MenuEditRequestDto menuReq)
        {
            if (await _sysMenuRepository.ExistsAsync(x => x.Id != menuReq.Id && x.ParentId == menuReq.ParentId && x.Name == menuReq.Name))
            {
                throw new BusinessException("菜单名称重复");
            }
            if (await _sysMenuRepository.ExistsAsync(x => x.Id != menuReq.Id && x.Code == menuReq.Code))
            {
                throw new BusinessException("菜单编号重复");
            }
            var menu = await _sysMenuRepository.FindAsync(menuReq.Id);
            _mapper.Map(menuReq, menu);
            
            await _sysMenuRepository.UpdateAsync(menu);
            return menu.Id;
        }

        /// <summary>
        /// 加载菜单
        /// </summary>
        /// <param name="filter">过滤条件</param>
        /// <param name="loadAction">是否加载非菜单的数据</param>
        /// <param name="loadSystem">是否加载系统菜单的数据</param>
        /// <returns></returns>
        public async Task<List<SysMenuDto>> GetMenuAsync(SysMenuFilter filter, bool loadAction, bool loadSystem)
        {
            Expression<Func<SysMenu, bool>> exp = x => true;
            exp = exp.AndIf(!loadAction, x => x.IsMenu)
                .AndIf(!loadSystem, x => !x.IsSystem)
                .AndIf(filter?.Name.IsNotBlank() == true, x => x.Name.Contains(filter.Name))
                .AndIf(filter?.Code.IsNotBlank() == true, x => x.Code == filter.Code);

            var menuList = await _sysMenuRepository.Queryable(false).Where(exp).Select(x => new SysMenuDto
            {
                Id = x.Id,
                ParentId = x.ParentId,
                Name = x.Name,
                IsMenu = x.IsMenu,
                Code = x.Code,
                Url = x.Url,
                Icon = x.Icon,
                Order = x.Order,
                Remark = x.Remark,
                CreateTime = x.CreateTime
            }).ToListAsync();
            return menuList.ToList();
        }

        /// <summary>
        /// 加载菜单树状结构
        /// </summary>
        /// <param name="loadAction">是否加载非菜单的数据</param>
        /// <param name="loadSystem">是否加载系统菜单</param>
        /// <returns></returns>
        public async Task<List<TreeResponseDto>> GetMenuTreeAsync(bool loadAction, bool loadSystem)
        {
            var menuList = await GetMenuAsync(null, loadAction, loadSystem);
            var menuTree = DoGetChildren(string.Empty, menuList);
            return menuTree;
        }

        /// <summary>
        /// 递归获取菜单树
        /// </summary>
        /// <param name="parentId">父级id</param>
        /// <param name="menuList">菜单原始列表</param>
        /// <returns></returns>
        private List<TreeResponseDto> DoGetChildren(string parentId, IEnumerable<SysMenuDto> menuList)
        {
            var children = menuList
                .WhereIf(parentId.IsBlank(), x => x.ParentId.IsBlank())
                .WhereIf(parentId.IsNotBlank(), x => x.ParentId == parentId)
                .OrderBy(x => x.Order)
                .Select(x => new TreeResponseDto
                {
                    Key = x.Id,
                    Title = x.Name,
                    Value = x.Id,
                }).ToList();
            foreach (var tree in children)
            {
                tree.Children = DoGetChildren(tree.Key, menuList);
            }
            return children;
        }

        /// <summary>
        /// 加载菜单表格树状结构
        /// </summary>
        public async Task<List<SysMenuDto>> GetMenuTableTreeAsync(SysMenuFilter filter)
        {
            var menuList = await GetMenuAsync(filter, true, true);
            var menuTree = DoGetTableTreeChildren(string.Empty, menuList);
            return menuTree;
        }

        /// <summary>
        /// 递归获取菜单树
        /// </summary>
        /// <param name="parentId">父级id</param>
        /// <param name="menuList">菜单原始列表</param>
        /// <returns></returns>
        private List<SysMenuDto> DoGetTableTreeChildren(string parentId, IEnumerable<SysMenuDto> menuList)
        {
            var children = menuList
                .WhereIf(parentId.IsBlank(), x => x.ParentId.IsBlank())
                .WhereIf(parentId.IsNotBlank(), x => x.ParentId == parentId)
                .OrderBy(x => x.Order)
                .ToList();
            foreach (var tree in children)
            {
                tree.Children = DoGetTableTreeChildren(tree.Id, menuList);
                if (!tree.Children.AnyOne())
                {
                    tree.Children = null;
                }
            }
            return children;
        }
    }
}
